/**
 * Copyright (C) 2013 DaiKit.com - daikit4gxt module (admin@daikit.com)
 *
 *         Project home : http://code.daikit.com/daikit4gxt
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.daikit.daikit4gxt.client.ui.forms;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import com.daikit.commons.shared.bean.AbstractDkBeanWithId;
import com.daikit.daikit4gxt.client.DkMain;
import com.daikit.daikit4gxt.client.ui.UIInvalidatable;
import com.daikit.daikit4gxt.client.ui.edit.DualGridAddLeftEvent;
import com.daikit.daikit4gxt.client.ui.edit.DualGridAddLeftEvent.DualGridAddLeftHandler;
import com.daikit.daikit4gxt.client.ui.edit.DualGridAddLeftEvent.HasDualGridAddLeftHandlers;
import com.daikit.daikit4gxt.client.ui.edit.DualGridAddRightEvent;
import com.daikit.daikit4gxt.client.ui.edit.DualGridAddRightEvent.DualGridAddRightHandler;
import com.daikit.daikit4gxt.client.ui.edit.DualGridAddRightEvent.HasDualGridAddRightHandlers;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.sencha.gxt.core.client.util.Margins;
import com.sencha.gxt.data.shared.ListStore;
import com.sencha.gxt.dnd.core.client.DND.Feedback;
import com.sencha.gxt.dnd.core.client.DND.Operation;
import com.sencha.gxt.dnd.core.client.GridDragSource;
import com.sencha.gxt.dnd.core.client.GridDropTarget;
import com.sencha.gxt.widget.core.client.Component;
import com.sencha.gxt.widget.core.client.button.IconButton;
import com.sencha.gxt.widget.core.client.button.IconButton.IconConfig;
import com.sencha.gxt.widget.core.client.container.HorizontalLayoutContainer;
import com.sencha.gxt.widget.core.client.container.HorizontalLayoutContainer.HorizontalLayoutData;
import com.sencha.gxt.widget.core.client.container.SimpleContainer;
import com.sencha.gxt.widget.core.client.event.SelectEvent;
import com.sencha.gxt.widget.core.client.event.SelectEvent.SelectHandler;
import com.sencha.gxt.widget.core.client.grid.Grid;


/**
 * A dual Grid view.
 * 
 * @author tcaselli
 * @version $Revision$ Last modifier: $Author$ Last commit: $Date$
 * @param <M1>
 *           type of bean in left grid (extends {@link AbstractDkBeanWithId})
 * @param <M2>
 *           type of bean in right grid (extends {@link AbstractDkBeanWithId})
 */
public abstract class MyDualGrid<M1 extends AbstractDkBeanWithId, M2 extends AbstractDkBeanWithId> extends
		AbstractDkHideableAdapterField<List<M2>> implements HasDualGridAddLeftHandlers<M1>, HasDualGridAddRightHandlers<M2>,
		UIInvalidatable
{

	@SuppressWarnings("javadoc")
	public interface MyDualGridAppearance
	{
		IconConfig top();

		IconConfig allLeft();

		IconConfig allRight();

		IconConfig down();

		IconConfig left();

		IconConfig right();

		IconConfig up();

		IconConfig bottom();
	}

	/**
	 * The DND mode enumeration. Default is MOVE_APPEND
	 */
	public enum DragNDropMode
	{
		/**
		 * Mode that permits to append not already existing entries in the target but does not remove the entry from the
		 * source
		 */
		COPY_APPEND,
		/**
		 * Mode that permits to insert not already existing entries in the target but does not remove the entry from the
		 * source
		 */
		COPY_INSERT,
		/**
		 * Mode that permits to append not already existing entries in the target and remove the entry from the source.<br>
		 * This is the default.
		 */
		MOVE_APPEND,
		/**
		 * Mode that permits to insert not already existing entries in the target and remove the entry from the source
		 */
		MOVE_INSERT,
		/**
		 * Mode that permits to only move elements within either left or right grid
		 */
		ONLY_GRID_INTERNAL_INSERT;

		/**
		 * @return whether this mode is an append mode (COPY_APPEND or MOVE_APPEND)
		 */
		boolean isModeAppend()
		{
			return COPY_APPEND.equals(this) || MOVE_APPEND.equals(this);
		}

		/**
		 * @return whether this mode is an insert mode (COPY_INSERT or MOVE_INSERT)
		 */
		boolean isModeInsert()
		{
			return COPY_INSERT.equals(this) || MOVE_INSERT.equals(this) || ONLY_GRID_INTERNAL_INSERT.equals(this);
		}

		/**
		 * @return whether this mode is copy mode mode (COPY_APPEND or COPY_INSERT)
		 */
		boolean isModeCopy()
		{
			return COPY_APPEND.equals(this) || COPY_INSERT.equals(this);
		}

		/**
		 * @return whether this mode is a move mode (MOVE_APPEND or MOVE_INSERT)
		 */
		boolean isModeMove()
		{
			return MOVE_APPEND.equals(this) || MOVE_INSERT.equals(this);
		}
	}

	protected GridDragSource<M1> sourceLeftField;
	protected GridDragSource<M2> sourceRightField;
	protected GridDropTarget<M2> targetRightField;
	protected GridDropTarget<M1> targetLeftField;

	private final HorizontalLayoutContainer horizontalLayoutContainer;
	private final VerticalPanel buttonBar;

	private final Component leftComponent;
	private final Component rightComponent;
	private final Grid<M1> leftGrid;
	private final Grid<M2> rightGrid;

	private IconButton top, up, allRight, right, left, allLeft, down, bottom;

	private final MyDualGridAppearance appearance;

	private final String dndGroup = getId() + "-group";
	private final String dndGroupRight = getId() + "-group-right";

	private boolean enableDnd = false;
	private boolean enabled = true;

	protected DragNDropMode mode = DragNDropMode.MOVE_INSERT;

	private final Set<DualFunctionality> setOfFunctionalities;

	/**
	 * Functionalities that the dual view may have
	 * 
	 * @author tcaselli
	 * @version $Revision$ Last modifier: $Author$ Last commit: $Date$
	 */
	public static enum DualFunctionality
	{
		/**
		 * To add all functionalities
		 */
		ALL,
		/**
		 * To add the "Move top" functionality
		 */
		TOP,
		/**
		 * To add the "Move all right" functionality
		 */
		ALL_RIGHT,
		/**
		 * To add the "Move all left" functionality
		 */
		ALL_LEFT,
		/**
		 * To add the "Move right" functionality
		 */
		RIGHT,
		/**
		 * To add the "Move left" functionality
		 */
		LEFT,
		/**
		 * To add the "Move up" functionality
		 */
		UP,
		/**
		 * To add the "Move down" functionality
		 */
		DOWN,
		/**
		 * To add the "Move bottom" functionality
		 */
		BOTTOM
	}

	/**
	 * Constructor of {@link MyDualGrid}
	 * 
	 * @param leftComponent
	 *           the left component of the dual view
	 * @param rightComponent
	 *           the right component of the dual view
	 * @param dualFunctionalities
	 *           a succession of {@link DualFunctionality} (don't fill this argument or give
	 *           {@link DualFunctionality#ALL} to have all functionalities)
	 */
	public MyDualGrid(final Component leftComponent, final Component rightComponent,
			final DualFunctionality... dualFunctionalities)
	{
		super(new SimpleContainer());
		horizontalLayoutContainer = new HorizontalLayoutContainer();
		((SimpleContainer) getWidget()).add(horizontalLayoutContainer);
		this.appearance = GWT.create(MyDualGridAppearance.class);

		setOfFunctionalities = new HashSet<DualFunctionality>();
		for (final DualFunctionality functionality : dualFunctionalities)
		{
			setOfFunctionalities.add(functionality);
		}

		buttonBar = new VerticalPanel();
		buttonBar.setSpacing(5);
		buttonBar.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);

		final boolean addAllFunctionalities = setOfFunctionalities.isEmpty()
				|| setOfFunctionalities.contains(DualFunctionality.ALL);

		if (addAllFunctionalities || setOfFunctionalities.contains(DualFunctionality.TOP))
		{
			top = new IconButton(appearance.top());
			top.setToolTip(DkMain.i18n().label_move_top());
			top.addSelectHandler(new SelectHandler()
			{

				@Override
				public void onSelect(final SelectEvent event)
				{
					onTop();
				}
			});
			buttonBar.add(top);
		}

		if (addAllFunctionalities || setOfFunctionalities.contains(DualFunctionality.UP))
		{
			up = new IconButton(appearance.up());
			up.setToolTip(DkMain.i18n().label_move_up());
			up.addSelectHandler(new SelectHandler()
			{

				@Override
				public void onSelect(final SelectEvent event)
				{
					onUp();
				}
			});
			buttonBar.add(up);
		}

		if (addAllFunctionalities || setOfFunctionalities.contains(DualFunctionality.ALL_RIGHT))
		{
			allRight = new IconButton(appearance.allRight());
			allRight.setToolTip(DkMain.i18n().label_add_all());
			allRight.addSelectHandler(new SelectHandler()
			{
				@Override
				public void onSelect(final SelectEvent event)
				{
					onAllRight();
				}
			});
			buttonBar.add(allRight);
		}

		if (addAllFunctionalities || setOfFunctionalities.contains(DualFunctionality.RIGHT))
		{
			right = new IconButton(appearance.right());
			right.setToolTip(DkMain.i18n().label_add());
			right.addSelectHandler(new SelectHandler()
			{

				@Override
				public void onSelect(final SelectEvent event)
				{
					onRight();
				}
			});
			buttonBar.add(right);
		}

		if (addAllFunctionalities || setOfFunctionalities.contains(DualFunctionality.LEFT))
		{
			left = new IconButton(appearance.left());
			left.setToolTip(DkMain.i18n().label_remove());
			left.addSelectHandler(new SelectHandler()
			{
				@Override
				public void onSelect(final SelectEvent event)
				{
					onLeft();
				}
			});
			buttonBar.add(left);
		}

		if (addAllFunctionalities || setOfFunctionalities.contains(DualFunctionality.ALL_LEFT))
		{
			allLeft = new IconButton(appearance.allLeft());
			allLeft.setToolTip(DkMain.i18n().label_remove_all());
			allLeft.addSelectHandler(new SelectHandler()
			{
				@Override
				public void onSelect(final SelectEvent event)
				{
					onAllLeft();
				}
			});
			buttonBar.add(allLeft);
		}

		if (addAllFunctionalities || setOfFunctionalities.contains(DualFunctionality.DOWN))
		{
			down = new IconButton(appearance.down());
			down.setToolTip(DkMain.i18n().label_move_down());
			down.addSelectHandler(new SelectHandler()
			{

				@Override
				public void onSelect(final SelectEvent event)
				{
					onDown();
				}
			});
			buttonBar.add(down);
		}

		if (addAllFunctionalities || setOfFunctionalities.contains(DualFunctionality.BOTTOM))
		{
			bottom = new IconButton(appearance.bottom());
			bottom.setToolTip(DkMain.i18n().label_move_bottom());
			bottom.addSelectHandler(new SelectHandler()
			{

				@Override
				public void onSelect(final SelectEvent event)
				{
					onBottom();
				}
			});
			buttonBar.add(bottom);
		}

		this.leftComponent = leftComponent;
		this.rightComponent = rightComponent;

		leftGrid = getLeftGrid(leftComponent);
		rightGrid = getRightGrid(rightComponent);

		horizontalLayoutContainer.add(leftComponent, new HorizontalLayoutData(-1, 1));
		horizontalLayoutContainer.add(buttonBar, new HorizontalLayoutData(1, -1, new Margins(50, 7, 0, 7)));
		horizontalLayoutContainer.add(rightComponent, new HorizontalLayoutData(-1, 1));
		setMode(mode);
		setEnableDnd(enableDnd);

		setHeight(DkMain.config().getEditorGridHeight());
	}

	protected abstract Grid<M1> getLeftGrid(final Component leftComponent);

	protected abstract Grid<M2> getRightGrid(final Component rightComponent);

	protected abstract M1 convertRightElement(M2 rightElement);

	protected abstract M2 convertLeftElement(M1 leftElement);

	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
	// BUTTONS CALLBACKS
	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

	private List<M1> convertRightElements(final List<M2> rightElements)
	{
		final List<M1> converted = new ArrayList<M1>();
		for (final M2 rightElement : rightElements)
		{
			final M1 convertedElement = convertRightElement(rightElement);
			if (convertedElement != null)
			{
				converted.add(convertedElement);
			}
		}
		return converted;
	}

	private List<M2> convertLeftElements(final List<M1> leftElements)
	{
		final List<M2> converted = new ArrayList<M2>();
		for (final M1 leftElement : leftElements)
		{
			final M2 convertedElement = convertLeftElement(leftElement);
			if (convertedElement != null)
			{
				converted.add(convertedElement);
			}
		}
		return converted;
	}

	private List<M1> getLeftItemsNotAlreadyPresent(final List<M1> selection)
	{
		final List<M1> toAddToLeft = new ArrayList<M1>();
		final List<M1> allLeft = leftGrid.getStore().getAll();
		for (final M1 converted : selection)
		{
			if (!allLeft.contains(converted))
			{
				toAddToLeft.add(converted);
			}
		}
		return toAddToLeft;
	}

	private List<M2> getRightItemsNotAlreadyPresent(final List<M2> selection)
	{
		final List<M2> toAddToRight = new ArrayList<M2>();
		final List<M2> allRight = rightGrid.getStore().getAll();
		for (final M2 converted : selection)
		{
			if (!allRight.contains(converted))
			{
				toAddToRight.add(converted);
			}
		}
		return toAddToRight;
	}

	protected void onAllLeft()
	{
		final List<M2> selection = rightGrid.getStore().getAll();
		final List<M1> converted = getLeftItemsNotAlreadyPresent(convertRightElements(selection));
		rightGrid.getStore().clear();
		if (!converted.isEmpty())
		{
			leftGrid.getStore().addAll(converted);
			leftGrid.getSelectionModel().select(converted, false);
			final DualGridAddLeftEvent<M1> leftEvent = new DualGridAddLeftEvent<M1>(converted);
			fireEvent(leftEvent);
		}
	}

	protected void onLeft()
	{
		final List<M2> selection = rightGrid.getSelectionModel().getSelectedItems();
		final List<M1> converted = getLeftItemsNotAlreadyPresent(convertRightElements(selection));
		for (final M2 m2 : selection)
		{
			rightGrid.getStore().remove(m2);
		}
		if (!converted.isEmpty())
		{
			leftGrid.getStore().addAll(converted);
			leftGrid.getSelectionModel().select(converted, false);
			final DualGridAddLeftEvent<M1> leftEvent = new DualGridAddLeftEvent<M1>(converted);
			fireEvent(leftEvent);
		}
	}

	protected void onRight()
	{
		final List<M1> selection = leftGrid.getSelectionModel().getSelectedItems();
		final List<M2> converted = getRightItemsNotAlreadyPresent(convertLeftElements(selection));
		if (mode.isModeMove())
		{
			for (final M1 m1 : selection)
			{
				leftGrid.getStore().remove(m1);
			}
		}
		if (!converted.isEmpty())
		{
			rightGrid.getStore().addAll(converted);
			rightGrid.getSelectionModel().select(converted, false);
			final DualGridAddRightEvent<M2> rightEvent = new DualGridAddRightEvent<M2>(converted);
			fireEvent(rightEvent);
		}
	}


	protected void onAllRight()
	{
		final List<M1> selection = leftGrid.getStore().getAll();
		final List<M2> converted = getRightItemsNotAlreadyPresent(convertLeftElements(selection));
		if (mode.isModeMove())
		{
			leftGrid.getStore().clear();
		}
		if (!converted.isEmpty())
		{
			rightGrid.getStore().addAll(converted);
			rightGrid.getSelectionModel().select(converted, false);
			final DualGridAddRightEvent<M2> rightEvent = new DualGridAddRightEvent<M2>(converted);
			fireEvent(rightEvent);
		}
	}

	protected void onUp()
	{
		final List<M2> selection = rightGrid.getSelectionModel().getSelectedItems();

		Collections.sort(selection, new Comparator<M2>()
		{
			@Override
			public int compare(final M2 object1, final M2 object2)
			{
				return rightGrid.getStore().indexOf(object1) > rightGrid.getStore().indexOf(object2) ? 1 : 0;
			}
		});

		for (final M2 m : selection)
		{
			final int idx = rightGrid.getStore().indexOf(m);
			if (idx > 0)
			{
				rightGrid.getStore().remove(m);
				rightGrid.getStore().add(idx - 1, m);
			}
		}
		rightGrid.getSelectionModel().select(selection, false);
	}

	protected void onTop()
	{
		final List<M2> selection = rightGrid.getSelectionModel().getSelectedItems();

		Collections.sort(selection, new Comparator<M2>()
		{
			@Override
			public int compare(final M2 object1, final M2 object2)
			{
				return rightGrid.getStore().indexOf(object1) > rightGrid.getStore().indexOf(object2) ? 1 : 0;
			}
		});

		for (int i = 0; i < selection.size(); i++)
		{
			final M2 m = selection.get(i);
			rightGrid.getStore().remove(m);
			rightGrid.getStore().add(i, m);
		}
		rightGrid.getSelectionModel().select(selection, false);
	}

	protected void onDown()
	{
		final List<M2> selection = rightGrid.getSelectionModel().getSelectedItems();

		Collections.sort(selection, new Comparator<M2>()
		{
			@Override
			public int compare(final M2 object1, final M2 object2)
			{
				return rightGrid.getStore().indexOf(object1) < rightGrid.getStore().indexOf(object2) ? 1 : 0;
			}
		});

		for (final M2 m : selection)
		{
			final int idx = rightGrid.getStore().indexOf(m);
			if (idx < rightGrid.getStore().size() - 1)
			{
				rightGrid.getStore().remove(m);
				rightGrid.getStore().add(idx + 1, m);
			}
		}
		rightGrid.getSelectionModel().select(selection, false);
	}

	protected void onBottom()
	{
		final List<M2> selection = rightGrid.getSelectionModel().getSelectedItems();

		Collections.sort(selection, new Comparator<M2>()
		{
			@Override
			public int compare(final M2 object1, final M2 object2)
			{
				return rightGrid.getStore().indexOf(object1) < rightGrid.getStore().indexOf(object2) ? 1 : 0;
			}
		});

		final List<M2> all = rightGrid.getStore().getAll();

		for (int i = 0; i < selection.size(); i++)
		{
			final M2 m = selection.get(i);
			rightGrid.getStore().remove(m);
			rightGrid.getStore().add(all.size() - i, m);
		}
		rightGrid.getSelectionModel().select(selection, false);
	}

	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
	// METHODS
	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

	@SuppressWarnings("unchecked")
	@Override
	protected void onResize(final int width, final int height)
	{
		super.onResize(width, height);
		// margin left of middle = 7
		// margin right of middle = 7
		// 2* 2*1 px border
		final int w = (width - (27 + 7 + 7 + 2 + 2)) / 2;

		int heightLeft = height;
		int heightRight = height;

		if (getLeftComponent() instanceof MyEditorGrid && ((MyEditorGrid<M1>) getLeftComponent()).isHeaderVisible())
		{
			heightLeft -= 22;
		}
		if (getRightComponent() instanceof MyEditorGrid && ((MyEditorGrid<M2>) getRightComponent()).isHeaderVisible())
		{
			heightRight -= 22;
		}

		getLeftComponent().setPixelSize(w, heightLeft);
		getRightComponent().setPixelSize(w, heightRight);
	}

	@Override
	public HandlerRegistration addDualGridLeftAddHandler(final DualGridAddLeftHandler<M1> handler)
	{
		return addHandler(handler, DualGridAddLeftEvent.getType());
	}

	@Override
	public HandlerRegistration addDualGridRightAddHandler(final DualGridAddRightHandler<M2> handler)
	{
		return addHandler(handler, DualGridAddRightEvent.getType());
	}

	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
	// GETTERS / SETTERS
	// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-

	@Override
	public void setEnabled(final boolean enabled)
	{
		getLeftComponent().setEnabled(enabled);
		getRightComponent().setEnabled(enabled);
		if (top != null)
		{
			top.setEnabled(enabled);
		}
		if (up != null)
		{
			up.setEnabled(enabled);
		}
		if (allRight != null)
		{
			allRight.setEnabled(enabled);
		}
		if (right != null)
		{
			right.setEnabled(enabled);
		}
		if (left != null)
		{
			left.setEnabled(enabled);
		}
		if (allLeft != null)
		{
			allLeft.setEnabled(enabled);
		}
		if (down != null)
		{
			down.setEnabled(enabled);
		}
		if (bottom != null)
		{
			bottom.setEnabled(enabled);
		}
		this.enabled = enabled;
	}

	@Override
	public final boolean isEnabled()
	{
		return enabled;
	}

	/**
	 * @return the enableDnd
	 */
	public boolean isEnableDnd()
	{
		return enableDnd;
	}

	/**
	 * Returns the DND group name.
	 * 
	 * @return the group name
	 */
	public String getDndGroup()
	{
		return dndGroup;
	}

	/**
	 * Returns the left field's drag source instance.
	 * 
	 * @return the drag source
	 */
	public GridDragSource<M1> getDragSourceLeftField()
	{
		assert isOrWasAttached() : "Can only be called post-render";
		return sourceLeftField;
	}

	/**
	 * Returns the right field's drag source instance.
	 * 
	 * @return the drag source
	 */
	public GridDragSource<M2> getDragSourceRightField()
	{
		assert isOrWasAttached() : "Can only be called post-render";
		return sourceRightField;
	}

	/**
	 * Returns the left field's drop target instance.
	 * 
	 * @return the drag source
	 */
	public GridDropTarget<M1> getDropTargetLeftField()
	{
		assert isOrWasAttached() : "Can only be called post-render";
		return targetLeftField;
	}

	/**
	 * Returns the right field's drop target instance.
	 * 
	 * @return the drag source
	 */
	public GridDropTarget<M2> getDropTargetRightField()
	{
		assert isOrWasAttached() : "Can only be called post-render";
		return targetRightField;
	}

	/**
	 * Returns the list field's mode.
	 * 
	 * @return the mode
	 */
	public DragNDropMode getMode()
	{
		return mode;
	}

	/**
	 * Specifies if selections are either inserted or appended when moving between lists.
	 * 
	 * @param mode
	 *           the mode
	 */
	public void setMode(final DragNDropMode mode)
	{
		this.mode = mode;
	}

	/**
	 * The field value.
	 */
	@Override
	public List<M2> getValue()
	{
		return rightGrid.getSelectionModel().getSelectedItems();
	}

	@Override
	public void setValue(final List<M2> value)
	{
		rightGrid.getSelectionModel().setSelection(value);
	}

	/**
	 * Sets the drag and drop group name. A group name will be generated if none is specified.
	 * 
	 * @param group
	 *           the group name
	 * @param groupRight
	 *           the right group name (can be the same than left group name to allow cross drag and drop)
	 */
	public void setDndGroup(final String group, final String groupRight)
	{
		if (sourceLeftField != null)
		{
			sourceLeftField.setGroup(group);
		}
		if (sourceRightField != null)
		{
			sourceRightField.setGroup(groupRight);
		}
		if (targetLeftField != null)
		{
			targetLeftField.setGroup(group);
		}
		if (targetRightField != null)
		{
			targetRightField.setGroup(groupRight);
		}
	}

	/**
	 * True to allow selections to be dragged and dropped between lists (defaults to true).
	 * 
	 * @param enableDnd
	 *           true to enable drag and drop
	 */
	public void setEnableDnd(final boolean enableDnd)
	{
		if (enableDnd)
		{
			if (sourceLeftField == null)
			{

				sourceLeftField = new GridDragSource<M1>(leftGrid);
				sourceRightField = new GridDragSource<M2>(rightGrid);
				targetLeftField = new GridDropTarget<M1>(leftGrid);
				targetRightField = new GridDropTarget<M2>(rightGrid);

				if (mode.isModeInsert())
				{
					targetRightField.setAllowSelfAsSource(true);
					targetLeftField.setFeedback(Feedback.INSERT);
					targetRightField.setFeedback(Feedback.INSERT);
				}
				else if (mode.isModeMove())
				{
					targetRightField.setAllowSelfAsSource(false);
					targetLeftField.setFeedback(Feedback.APPEND);
					targetRightField.setFeedback(Feedback.APPEND);
				}

				if (mode.isModeMove())
				{
					targetRightField.setOperation(Operation.MOVE);
					targetLeftField.setOperation(Operation.MOVE);
				}
				else if (mode.isModeCopy())
				{
					targetRightField.setOperation(Operation.COPY);
					targetLeftField.setOperation(Operation.COPY);
				}

				if (mode.equals(DragNDropMode.ONLY_GRID_INTERNAL_INSERT))
				{
					setDndGroup(dndGroup, dndGroupRight);
				}
				else
				{
					setDndGroup(dndGroup, dndGroup);
				}
			}

		}
		else
		{
			if (sourceLeftField != null)
			{
				sourceLeftField.release();
				sourceLeftField = null;
			}
			if (sourceRightField != null)
			{
				sourceRightField.release();
				sourceRightField = null;
			}
			if (targetLeftField != null)
			{
				targetLeftField.release();
				targetLeftField = null;
			}
			if (targetRightField != null)
			{
				targetRightField.release();
				targetRightField = null;
			}
		}
		this.enableDnd = enableDnd;
	}

	@Override
	public void setHeight(final int height)
	{
		getLeftComponent().setHeight(-1);
		getRightComponent().setHeight(-1);
		super.setHeight(height);
	}

	/**
	 * Set the offset top for the vertical button bar. Default is 60.
	 * 
	 * @param offsetInPixels
	 *           the new offset top to set
	 */
	public void setButtonBarOffsetTop(final int offsetInPixels)
	{
		buttonBar.getElement().getStyle().setMarginTop(offsetInPixels, Unit.PX);
	}

	/**
	 * @return the leftGrid
	 */
	public Grid<M1> getLeftGrid()
	{
		return leftGrid;
	}

	/**
	 * @return the rightGrid
	 */
	public Grid<M2> getRightGrid()
	{
		return rightGrid;
	}

	/**
	 * @return the leftComponent
	 */
	public Component getLeftComponent()
	{
		return leftComponent;
	}

	/**
	 * @return the rightComponent
	 */
	public Component getRightComponent()
	{
		return rightComponent;
	}

	/**
	 * @return the leftGrid.getStore()
	 */
	public ListStore<M1> getLeftStore()
	{
		return leftGrid.getStore();
	}

	/**
	 * @return the rightGrid.getStore()
	 */
	public ListStore<M2> getRightStore()
	{
		return rightGrid.getStore();
	}
}
